
#include "scheduler.h"

//static ready_queue_t ready_queue;
list ready_queue;
static uint32_t in_ready_queue[MAX_PRIORITY]; /* this array will count the number of processes of each priority level in the queue */

/* initializes the scheduler's data structures */
void initialize_scheduler(){
  ready_queue = NULL;
  int i;
  for(i=0;i<MAX_PRIORITY;i++)
    in_ready_queue[i] = 0;
}

/* inserts pcb in the ready queue */
int32_t insert_ready_queue(PCB* pcb, int reschedule){
  int32_t priority;
  list current, prev = NULL;
  priority = pcb_get_priority(pcb);
  for(current = ready_queue; current != NULL && priority >= pcb_get_priority((PCB*)current->data); prev = current, current = current->next);
  //pcb must now becomes the next of prev.
  if(linked_list_add_after_prev(&ready_queue, prev, pcb) == OUT_OF_MEMORY)
    return OUT_OF_MEMORY;
  //there is one more process with this priority
  in_ready_queue[priority-1]++;
  //check if the newcomer must preempt the current running process (reschedule indicates if this check has to be made (sometimes it is not useful like when processing the delayed) because it will be made just after
  if(reschedule && (running == NULL || pcb_get_priority(running) > pcb_get_priority(pcb))){
    kset_timer(TIME_QUANTUM); //allocates a full time quantum
    schedule();
  }
  return SUCCESS;
}

/* removes pcb from the ready queue */
int32_t remove_ready_queue(PCB* pcb){
  int32_t priority;
  if(linked_list_delete(&ready_queue, pcb, compare_pcb, NULL) == FAILURE){
    return FAILURE;
  }
  priority = pcb_get_priority(pcb);
  //there is one less element with this priority
  in_ready_queue[priority-1]--;
  //check if the guy that has been removed was running. That would mean we need to reschedule. 
  if(running == NULL || pcb_get_pid(running) == pcb_get_pid(pcb)){
    kset_timer(TIME_QUANTUM);
    schedule();
  }
  return SUCCESS;
}

/* returns the first element of the ready queue */
PCB* get_first(){
  return (ready_queue == NULL?NULL:(PCB*)ready_queue->data);
}

/* returns a pointer to the running PCB */
PCB* get_running(){
  return running;
}

void schedule(){
  PCB* first = get_first();
  list to_move,current,prev;
  int32_t first_prio;
  if(first != NULL){
    //was there a process before us?
    if(running != NULL){
      // do we have to move the first element of the ready queue at the end of the set of processes having the same priority as it? This is required to achieve the round robin feature. 
      first_prio = pcb_get_priority(first);
      if(pcb_get_pid(first) == pcb_get_pid(running) && in_ready_queue[first_prio - 1] > 1){
	//the guy that was running is also the first process of the ready queue and there are other processes with the same priority => round robin. We move it.
	to_move = ready_queue;
	ready_queue = to_move->next; //the second process becomes the first (and the one that will be scheduled)
	//inserting to_move at the correct place
	for(prev = NULL, current = ready_queue; current != NULL && pcb_get_priority((PCB*)current->data) == first_prio; prev = current, current = current->next);
	//let's insert to_move between prev and current
	to_move->next = prev->next;
	prev->next = to_move;
	//the process to schedule is the new first process of the ready queue
	first = get_first();
      }
    }
    pcb_set_state(first, RUNNING_STATE);
  }
  //first is the next process to be scheduled
  if(running != NULL && pcb_get_state(running) == RUNNING_STATE)
    pcb_set_state(running, READY_STATE);
  running = first;
}



